#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#define LISTENQ       10


int tcp_listen(const char *hostname, const char *service, socklen_t *addrlenp)
{
    int listenfd, n;
    int ret;
    const int on = 1;
    struct addrinfo hints, *res, *resave;

    /* set protocol to find out */
    bzero(&hints, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;     /* IPv4, IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* TCP */
    
    /* get addrinfo linked list */
    if ((n = getaddrinfo(hostname, service, &hints, &res)) != 0) {
        fprintf(stderr, "tcp_listen error for %s, %s: %s\n", hostname, service, gai_strerror(n));
        exit(1);
    }
    resave = res;
    

    /* traverse the linked list until success to create a socket for bind, listen */
    do {
        listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (listenfd < 0)
            continue;
        
        /* set REUSEADDR option to re-bind using the same socket fd */ 
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

        if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0) {
            break; /* success */
        }

        close(listenfd);
    } while((res = res->ai_next) != NULL);
    
    if (res == NULL) {
        fprintf(stderr, "tcp_listen error for %s, %s\n", hostname, service);
        exit(1);
    }

    ret = listen(listenfd, LISTENQ);
    if (ret < 0) {
        perror("listen error");
        exit(1);
    }

    /* return size of protocol address */
    if (addrlenp) {
        *addrlenp = res->ai_addrlen;
    }

    /* free addrinfo linked list */
    freeaddrinfo(resave);

    return listenfd;
}
